#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe
import logging
from aggmon.consumer import Consumer
from basic_types.metric import Metric
from mon_hierarchy import Hierarchy
from threading import Lock
from aggmon.aggmon_component import Component


class DPAggregator(Consumer):
    """
    Dual ported aggregator.
    Subscribes to two topics, the metric it should aggregate and the self-produced group aggregated metric.
    This only makes sense with group aggregators.
    """
    def __init__( self, metric_name, agg_metric_name, group_path, input_channel=None, group_channels=[], upstream_channels=[], hierarchy=None ):
        assert group_path is not None, "DP group aggregator needs group path"
        self.agg_metric_name = agg_metric_name
        self.metric_name = metric_name
        self._lock = Lock()
        if self.__class__.__name__.startswith( "Host" ):
            self._output_channels = group_channels
        else:
            self._output_channels = upstream_channels
        self._group_topic = Hierarchy.path_to_topic( group_path )

        # match aggregated metrics coming from our subgroups
        self._agg_topic = self._group_topic + ( len(self._group_topic) > 0 and "." or "" ) + "*.*." + agg_metric_name
        self.cons1 = Consumer.__init__( self, hierarchy=hierarchy, input_channel=input_channel, topic=self._agg_topic )
        
        # match metric published from child hosts (or child groups, but those won't publish this metric)
        self._topic = self._group_topic + ( len(self._group_topic) > 0 and "." or "" ) + "*.*." + metric_name
        self.cons2 = Consumer.__init__( self, hierarchy=hierarchy, input_channel=input_channel, topic=self._topic )

        # publishing group topic is "universe" if we're at the top of the hierarchy
        if len( self._group_topic ) == 0:
            self._group_topic = Hierarchy.ROOT_NAME

        logging.info( "DPAggregator %s aggregates %s to %s, will publish to %s" %
                      (type(self).__name__, metric_name, agg_metric_name,
                       ", ".join( [ channel.url for channel in self._output_channels ] ) ) )

    def publish( self, metric ):
        "Construct topic and publish to all output channels."
        topic = self._group_topic + ".aggregator." + self.agg_metric_name
        for channel in self._output_channels:
            logging.debug( "DPAggregator '%s' publishing to channel %s with topic %s : %s"
                           % (type(self).__name__, channel.url, topic, repr(metric)) )
            channel.publish( topic, metric )

    def worthy_metric( self, message, topic ):
        """
        Decide whether message should be handled or dropped.
        Ignore self published messages if at the top of the hierarchy,
        ignore non-metric messages.
        """
        if (self._group_topic == Hierarchy.ROOT_NAME
            and topic.startswith( Hierarchy.ROOT_NAME + "." ) 
            and topic.endswith( "." + self.agg_metric_name ) ) \
            or not isinstance( message, Metric ):
            return False
        return True


class DPAggregatorComponent(Component):
    def __init__( self, *args, **kwds ):
        Component.__init__( self, *args, **kwds )
        self.allowed_kwds = ["input_channel", "group_channels", "upstream_channels", "hierarchy"]

    def instantiate( self, *args, **kwds ):
        # lose the reference to any previous instance
        self.instance = None
        try:
            logging.debug( "instantiating: %s" % self.name )
            self.instance = DPAggregator( *args, **kwds )
        except Exception, e:
            raise e

    def stop( self ):
        if self.instance.is_running():
            self.state = Component.STOPPED
            self.instance.cons1.unsubscribe()
            self.instance.cons2.unsubscribe()
